/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package net.worlds.net;

import java.awt.Color;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import net.worlds.WorldsCLI;
import net.worlds.WorldsException;
import net.worlds.Chat;
import net.worlds.Main;
import net.worlds.object.GameObject;
import net.worlds.object.NetworkServer;
import net.worlds.object.Part;
import net.worlds.object.Player;
import net.worlds.object.Sound;
import net.worlds.object.Tool;
import org.jbox2d.common.Vec2D;

public class Server implements Runnable {

    private int port;
    private int maxPlayers;
    private int gameID = -1;
    private ServerSocket server;
    private final ArrayList<RemotePlayer> players = new ArrayList<RemotePlayer>(16);
    private Thread serverThread;
    private Thread heartbeatThread;
    public boolean isBuildServer = false;
    public boolean running = true;
    public HashMap<RemotePlayer, PlayerCursor> cursors = new HashMap<RemotePlayer, PlayerCursor>(32);

    public Server(int port, int maxPlayers, int gameID) {
        this.port = port;
        this.maxPlayers = maxPlayers;
        this.gameID = gameID;
        try {
            server = new ServerSocket(port);
            Main.log("Starting server on port " + port);
            serverThread = new Thread(this);
            serverThread.start();
            heartbeatThread = new Thread(new Heartbeat());
            heartbeatThread.start();
        } catch (IOException ex) {
            Main.log("Error starting server: "+ex);
        }
    }

    public ArrayList<RemotePlayer> getPlayers()
    {
        return players;
    }

    public static void start(int port) {
        try {
            NetworkServer s = (NetworkServer) Main.getGame().getMainWorld().getService("NetworkServer");
            s.setProp("Port", port, true);
            s.start();
        } catch (WorldsException ex) {
            ex.printStackTrace();
        }
    }

    public void stop()
    {
        Main.setServer(null);
        running = false;
        synchronized(players)
        {
            for(RemotePlayer p : players)
            {
                p.disconnect();
            }
        }
        try {
            if(server != null)
                server.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    public void sendObject(GameObject obj)
    {
        synchronized(players)
        {
            for(RemotePlayer p : players)
            {
                p.sendObject(obj);
            }
        }
    }

    public void sendObjectRecursive(GameObject obj)
    {
        synchronized(players)
        {
            for(RemotePlayer p : players)
            {
                p.sendObjectRecursive(obj);
            }
        }
    }

    public void sendObjectCreate(GameObject obj, RemotePlayer ignorePlayer) {
        synchronized(players)
        {
            for (RemotePlayer p : players) {
                if(p != ignorePlayer)
                {
                    p.sendObjectCreate(obj);
                }
            }
        }
    }

    public void sendProp(GameObject obj, String key, Object value, boolean fireEvent, RemotePlayer ignorePlayer) {
        synchronized(players)
        {
            for (RemotePlayer p : players) {
                if(p != ignorePlayer)
                    p.sendProp(obj, key, value, fireEvent);
            }
        }
    }

    public void removePlayer(RemotePlayer p) {
        synchronized(players)
        {
            players.remove(p);
        }
        broadcastChat(new Color(85, 101, 131), p.getName()+" left the game");
    }

    public void sendWorldStatus(int status)
    {
        synchronized(players)
        {
            for(RemotePlayer p : players)
            {
                System.err.println("Status: "+status);
                p.sendWorldStatus(status);
            }
        }
    }

    public void sendInUse(GameObject obj, boolean v)
    {
        synchronized(players)
        {
            for(RemotePlayer p : players)
            {
                p.sendInUse(obj, v);
            }
        }
    }

    public void sendConsoleOutput(String text)
    {
        synchronized(players)
        {
            for(RemotePlayer p : players)
                p.sendConsoleOutput(text);
        }
    }

    public void sendPlayerRemove(RemotePlayer p)
    {
        if(p.getConnection() != null && p.getConnection().getPlayer() != null)
        {
            if(cursors.get(p) != null)
            {
                synchronized(players)
                {
                    for(RemotePlayer other : players)
                    {
                        other.sendCursorPosition(p, new Vec2D(0, Float.MIN_VALUE));
                    }
                }
            }
        cursors.remove(p);
        }
    }

    public void sendIDChange(int oldID, int newID)
    {
        synchronized(players)
        {
            for(RemotePlayer p : players)
            {
                p.sendIDChange(oldID, newID);
            }
        }
    }

    public void broadcastChat(RemotePlayer player, String msg)
    {
        Chat.addChat(java.awt.Color.white, player.getName()+"> "+msg);
        synchronized(players)
        {
            for(RemotePlayer p : players)
            {
                p.sendChat(new Color(255, 255, 255), player.getName()+"> "+msg);
            }
        }
    }
    public void broadcastChat(Color color, String msg)
    {
        Chat.addChat(color, msg);
        synchronized(players)
        {
            for(RemotePlayer p : players)
            {
                p.sendChat(color, msg);
            }
        }
    }
    public void sendMoveTo(Part part, int action)
    {
        synchronized(players)
        {
            for(RemotePlayer p : players)
            {
                p.sendMoveTo(part, action);
            }
        }
    }
    public void run() {
        try {
            System.err.println("READY:");
            while (!WorldsCLI.exit) {
                Socket socket = server.accept();
                synchronized(players)
                {
                    players.add(new RemotePlayer(socket));
                }
            }
            server.close();
        } catch (IOException ex) {
            if (!server.isClosed()) {
                try {
                    server.close();
                } catch (IOException ex1) {
                    ex.printStackTrace();
                }
            }
        }
    }
    private String playerNames()
    {
        StringBuilder b = new StringBuilder();
        int i = 0;
        synchronized(players)
        {
            for(RemotePlayer p : players)
            {
                if(p.levelSent)
                    b.append(p.getName());
                if(i != players.size() - 1)
                    b.append(", ");
                i++;
            }
        }
        return b.toString();
    }
    private void sendHeartbeat()
    {
        try {
            StringBuilder b = new StringBuilder();
            b.append("port="+port);
            b.append("&maxplayers="+maxPlayers);
            b.append("&players="+players.size());
            b.append("&playerlist="+URLEncoder.encode(playerNames(), "UTF-8"));
            b.append("&game="+gameID);
            if(Main.noView)
                b.append("&grid=true");
            byte[] bytes = b.toString().getBytes();
            URL url = new URL("http://"+Main.siteURL+"/game/ping.php");
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            conn.setRequestProperty("Content-Length", String.valueOf(bytes.length));
            conn.setRequestProperty("Content-Language", "en-US");
            conn.setUseCaches(false);
            conn.setDoInput(true);
            conn.setDoOutput(true);
            conn.connect();
            conn.getOutputStream().write(bytes);
            BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String result = rdr.readLine();
            Main.log(result);
            
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    private class Heartbeat implements Runnable
    {
        public void run() {
            while(!WorldsCLI.exit)
            {
                sendHeartbeat();
                try {
                    Thread.sleep(60 * 1000);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
    public void sendMessage(String text)
    {
        synchronized(players)
        {
            for(RemotePlayer p : players)
            {
                p.sendMessage(0, text);
            }
        }
    }
    public void sendPlayerMessage(Player player, String text)
    {
        synchronized(players)
        {
            for(RemotePlayer p : players)
            {
                if(p.getConnection() != null && p.getConnection().getPlayer() == player)
                {
                    p.sendMessage(1, text);
                    break;
                }
            }
        }
    }
    public void kick(Player player, String message)
    {
        synchronized(players)
        {
            for(RemotePlayer p : players)
            {
                if(p.getConnection() != null && p.getConnection().getPlayer() == player)
                {
                    p.sendKick(message);
                    break;
                }
            }
        }
    }
    public void sendCharacterSelectedTool(net.worlds.object.Character c, Tool t)
    {
        synchronized(players)
        {
            for(RemotePlayer p : players)
            {
                try {
                    if (p.getConnection() != null && p.getConnection().getPlayer() != null && p.getConnection().getPlayer().getObjectProp("Character") != null && p.getConnection().getPlayer().getObjectProp("Character") != c) {
                        p.sendSelectedTool(c, t);
                    }
                } catch (WorldsException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
    public void sendCharacterArmRotation(net.worlds.object.Character c, double r)
    {
        synchronized(players)
        {
            for(RemotePlayer p : players)
            {
                try {
                    if (p.getConnection() != null && p.getConnection().getPlayer() != null && p.getConnection().getPlayer().getObjectProp("Character") != null && p.getConnection().getPlayer().getObjectProp("Character") != c) {
                        p.sendArmRotation(c, r);
                    }
                } catch (WorldsException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
    public void sendSoundPlay(Sound s)
    {
        synchronized(players)
        {
            for(RemotePlayer p : players)
            {
                p.sendSoundPlay(s);
            }
        }
    }
    public void sendSoundStop(Sound s)
    {
        synchronized(players)
        {
            for(RemotePlayer p : players)
            {
                p.sendSoundStop(s);
            }
        }
    }
}
